import glob from 'glob';
import path from 'path';
import { EventEmitter } from 'events';
import {createI18n } from '@i18n-chain/node';
import { Controller } from '../routers/Controller';
import { PluginGlobal } from '../plugins/PluginGlobal';
import enUs from '../i18n/en-US';
import { I18nMessage } from '../i18n';
import { setInspector } from '../utils/setInspector';
import { Composer, compose } from '../utils/compose';

export abstract class Application extends EventEmitter {
  public readonly i18n: I18nMessage;

  protected readonly routesPath: string[];
  protected readonly composer: Composer;
  protected readonly noGlobalComposer: Composer;
  protected readonly pluginGlobal: PluginGlobal;

  constructor(routesPath: string | string[], locale: string) {
    super();

    this.i18n = this.createI18n(locale);
    this.composer = compose([]);
    this.noGlobalComposer = compose([]);
    this.pluginGlobal = new PluginGlobal();

    this.composer.append(this.noGlobalComposer);
    this.composer.append(this.pluginGlobal);

    this.routesPath = Array.isArray(routesPath) ? routesPath : [routesPath];
    this.searchRoutes(this.routesPath);

    setInspector(this);
  }

  public get locale(): string {
    return this.i18n._.getLocaleName();
  }

  public getPaths(): string[] {
    return this.routesPath;
  }

  /**
   * Useful for testing scenario.
   */
  protected append(routes: Controller | Controller[]): this {
    (Array.isArray(routes) ? routes : [routes]).forEach((router) => {
      this.parseControllers({ default: router });
    });

    return this;
  }

  protected createI18n(locale: string) {
    const names: string[] = [];
    const i18n = createI18n({
      defaultLocale: {
        key: 'en-US',
        values: enUs,
      },
    });

    glob.sync(path.join(__dirname, '..', 'i18n', '!(*.d).{ts,js}')).forEach((file) => {
      const name = path.basename(file, path.extname(file));
      if (name !== 'index') {
        i18n._.define(name, require(file).default);
        names.push(name);
      }
    });

    if (names.includes(locale)) {
      i18n._.locale(locale);
    }

    return i18n;
  }

  protected searchRoutes(routesPath: string[]): void {
    routesPath.forEach((routePath) => {
      glob.sync(path.resolve(routePath, '**/!(*.d).{ts,js}')).forEach((matchPath) => {
        const modules = require(matchPath);

        this.parseControllers(modules);
      });
    });
  }

  protected parseControllers(modules: Record<string, any>): void {
    const CustomController = this.getControllerInstance();

    Object.values(modules).forEach((item) => {
      if (item && item instanceof CustomController) {
        item.registerActions();

        const itemGlobalPlugins = item.getManager().getGlobalPlugins().getPlugins();

        if (itemGlobalPlugins.length) {
          const toLocalComposer = this.pluginGlobal.compare(itemGlobalPlugins);
          this.composer.append(item.createMiddleware(toLocalComposer));
        } else {
          this.noGlobalComposer.append(item.createMiddleware());
        }
      }
    });
  }

  protected abstract getControllerInstance(): new (...args: any[]) => Controller;

  protected abstract inspect(): object;
  protected abstract toJSON(): object;
}
